<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 6.1.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/xx1997/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/xx1997/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/xx1997/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/xx1997/images/logo.svg" color="#222">

<link rel="stylesheet" href="/xx1997/css/main.css">


<link rel="stylesheet" href="/xx1997/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"xx1997.gitee.io","root":"/xx1997/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
  </script>

  <meta property="og:type" content="website">
<meta property="og:title" content="xxstar blog">
<meta property="og:url" content="https://xx1997.gitee.io/index.html">
<meta property="og:site_name" content="xxstar blog">
<meta property="og:locale" content="zh_CN">
<meta property="article:author" content="xxstar">
<meta name="twitter:card" content="summary">

<link rel="canonical" href="https://xx1997.gitee.io/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : true,
    isPost : false,
    lang   : 'zh-CN'
  };
</script>

  <title>xxstar blog</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/xx1997/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">xxstar blog</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/xx1997/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/xx1997/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/xx1997/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/xx1997/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content index posts-expand">
            
      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://xx1997.gitee.io/2022/05/11/Java%E7%9A%84SPI%E6%9C%BA%E5%88%B6/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/xx1997/images/xxstar.jpg">
      <meta itemprop="name" content="xxstar">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="xxstar blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/xx1997/2022/05/11/Java%E7%9A%84SPI%E6%9C%BA%E5%88%B6/" class="post-title-link" itemprop="url">Java的SPI机制</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-05-11 15:50:41" itemprop="dateCreated datePublished" datetime="2022-05-11T15:50:41+08:00">2022-05-11</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-05-12 11:41:10" itemprop="dateModified" datetime="2022-05-12T11:41:10+08:00">2022-05-12</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/xx1997/categories/programming/" itemprop="url" rel="index"><span itemprop="name">编程</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>SPI（Server Provider Interface）是JDK内置的一种服务提供发现机制，是Java提供的可用于第三方实现和扩展的机制，<strong>通过该机制，我们可以实现解耦，SPI接口方负责定义和提供默认实现，SPI调用方可以按需扩展</strong>，比如有个接口，想运行时动态的给它添加实现，你只需要添加一个实现。例如java.sql.Driver接口，其他厂商可以针对这个接口做出不同的实现，MySql和Orcale都有不同的实现在各自的jar里。</p>
<h3 id="SPI和API的区别"><a href="#SPI和API的区别" class="headerlink" title="SPI和API的区别"></a>SPI和API的区别</h3><p>API（Application Programming Interface），广义上来看就是接口，负责程序与程序之间进行协作的通道。简单讲就是被调用方提供服务，服务是由被调用方自己实现的，调用方只需要调用即可。</p>
<p>所以API和SPI的区别就很明显了，<strong>API的调用方只能依赖使用提供方的实现，SPI就如同可定制化的API一样，调用方可以自定义实现替换API提供的默认实现</strong>。</p>
<h2 id="SPI应用实例"><a href="#SPI应用实例" class="headerlink" title="SPI应用实例"></a>SPI应用实例</h2><p>如果需要实现SPI，需要在调用方的classpath的META-INF&#x2F;services目录创建以<strong>接口全限定名</strong>命名的文件，被调用方通过Java提供的ServiceLoader类来查询继承了该接口的实现类。</p>
<p>数据库DriverManager、Spring、ConfigurableBeanFactory等都用到了SPI机制，这里以数据库DriverManager为例，看看SPI是如何实现的。</p>
<p>DriverManager是jdbc里管理和注册不同数据库driver的工具类。针对一个数据库，可能会存在着不同的数据库驱动实现。我们在使用特定的驱动实现时，不希望修改现有的代码，而希望通过一个简单的配置就可以达到效果。在Java中定义了java.sql.Driver，并没有具体的实现，具体的实现都是由不同厂商来提供的。</p>
<p>在MySql的驱动jar（mysql-connector-java:8.0.11）中，在META-INF&#x2F;services目录下会有一个名字为java.sql.Driver的文件：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">com.mysql.cj.jdbc.Driver</span><br></pre></td></tr></table></figure>

<p>在Orcale的驱动jar（ojdbc6-11.2.0.4.jar）中，在META-INF&#x2F;services目录下会有一个名字为java.sql.Driver的文件：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">oracle.jdbc.OracleDriver</span><br></pre></td></tr></table></figure>

<p>这个文件里面分别指定了MySql和Orcale对java.sql.Driver接口的实现类名，在系统启动时初始化DriverManager类时</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> &#123;</span><br><span class="line">    loadInitialDrivers();</span><br><span class="line">    println(<span class="string">&quot;JDBC DriverManager initialized&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">loadInitialDrivers</span><span class="params">()</span> &#123;</span><br><span class="line">	<span class="comment">//省略部分代</span></span><br><span class="line">    AccessController.doPrivileged(<span class="keyword">new</span> <span class="title class_">PrivilegedAction</span>&lt;Void&gt;() &#123;</span><br><span class="line">        <span class="keyword">public</span> Void <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="comment">// 这里使用ServiceLoader类用来发现实现了java.sql.Driver接口的驱动类</span></span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 先创建查找java.sql.Driver接口实现的类的ServiceLoader服务</span></span><br><span class="line">            ServiceLoader&lt;Driver&gt; loadedDrivers = ServiceLoader.load(Driver.class);</span><br><span class="line">            Iterator&lt;Driver&gt; driversIterator = loadedDrivers.iterator();</span><br><span class="line"></span><br><span class="line">            <span class="keyword">try</span>&#123;</span><br><span class="line">                <span class="comment">// driversIterator.hasNext()会一个个遍历搜索classpath下以及jar包中</span></span><br><span class="line">                <span class="comment">// 所有的META-INF/services目录下的java.sql.Driver文件，并找到文件中的实现类的名字</span></span><br><span class="line">                <span class="keyword">while</span>(driversIterator.hasNext()) &#123;</span><br><span class="line">                    driversIterator.next();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">catch</span>(Throwable t) &#123;</span><br><span class="line">                <span class="comment">// Do nothing</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">	<span class="comment">//省略部分代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="自己实现SPI"><a href="#自己实现SPI" class="headerlink" title="自己实现SPI"></a>自己实现SPI</h2><p>用maven创建父子模块，项目结构如下</p>
<p><img src="https://gitee.com/xx1997/xx-picture/raw/master/img/SPI-Test.PNG"></p>
<p>创建提供方模块provider，并创建Swtich接口</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Switch</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">open</span><span class="params">()</span>;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">close</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>创建调用方模块Caller，并创建LampSwitch、WaterSwitch类实现Switch接口</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LampSwitch</span> <span class="keyword">implements</span> <span class="title class_">Switch</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">open</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;打开灯的开关&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">close</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;关闭灯的开关&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WaterSwitch</span> <span class="keyword">implements</span> <span class="title class_">Switch</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">open</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;打开水龙头的开关&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">close</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;关闭水龙头的开关&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>并在<strong>resources&#x2F;META-INF&#x2F;services</strong>创建<strong>com.provider.Switch</strong>文件并写入Switch接口的实现类</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">com.caller.WaterSwitch</span><br><span class="line">com.caller.LampSwitch</span><br></pre></td></tr></table></figure>

<p>在提供方模块provider创建SwitchService类，通过ServiceLoader，选取调用方com.provider.Switch文件的第一个Switch接口实现类</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SwitchService</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> Switch <span class="title function_">getSwitch</span><span class="params">()</span>&#123;</span><br><span class="line">        ServiceLoader&lt;Switch&gt; switchServiceLoader = ServiceLoader.load(Switch.class);</span><br><span class="line">        Optional&lt;Switch&gt; switchOpt = StreamSupport.stream(switchServiceLoader.spliterator(), <span class="literal">false</span>).findFirst();</span><br><span class="line">        <span class="keyword">return</span> switchOpt.orElse(<span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>测试代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SwitchTest</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">SwitchService</span> <span class="variable">service</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SwitchService</span>();</span><br><span class="line">        <span class="type">Switch</span> <span class="variable">aSwitch</span> <span class="operator">=</span> service.getSwitch();</span><br><span class="line">        aSwitch.open();</span><br><span class="line">        aSwitch.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 执行结果</span></span><br><span class="line">打开水龙头的开关</span><br><span class="line">关闭水龙头的开关</span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure>

<p>可以看到switch使用的com.provider.Switch第一个实现类com.caller.WaterSwitch</p>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://xx1997.gitee.io/2022/05/05/Java%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/xx1997/images/xxstar.jpg">
      <meta itemprop="name" content="xxstar">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="xxstar blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/xx1997/2022/05/05/Java%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD/" class="post-title-link" itemprop="url">Java类的加载</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-05-05 15:10:32" itemprop="dateCreated datePublished" datetime="2022-05-05T15:10:32+08:00">2022-05-05</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-05-12 17:49:11" itemprop="dateModified" datetime="2022-05-12T17:49:11+08:00">2022-05-12</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/xx1997/categories/programming/" itemprop="url" rel="index"><span itemprop="name">编程</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>“加载”（Loading）阶段是整个“类加载”（Class Loading）过程中的一个阶段。在加载阶段，Java虚拟机需要完成以下三件事情：</p>
<ol>
<li>通过一个类的全限定名来获取定义此类的二进制字节流。</li>
<li>将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。</li>
<li>在内存中生成一个代表这个类的java.lang.Class对象，作为方法区这个类的各种数据的访问入口。</li>
</ol>
<h2 id="类加载器"><a href="#类加载器" class="headerlink" title="类加载器"></a>类加载器</h2><p>“通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到Java虚拟机外部去实现，这个动作实现的代码就是<strong>类加载器</strong>。</p>
<p>类加载器虽然只用于实现类的加载动作，但它在Java程序中起到的作用却远超类加载阶段。对于任意一个类，都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性，每一个类加载器，都拥有一个独立的类名称空间。这句话可以表达得更通俗一些：比较两个类是否“相等”，只有在这两个类是由同一个类加载器加载的前提下才有意义，否则，即使这两个类来源于同一个Class文件，被同一个Java虚拟机加载，只要加载它们的类加载器不同，那这两个类就必定不相等。</p>
<p>从不同的角度看，类加载器也可以分为两类和三类，从JVM角度可以分为两类：</p>
<ul>
<li>启动类加载器（Bootstrap ClassLoader）：由c++语言实现，是虚拟机的一部分；</li>
<li>其他加载器：用Java实现，独立于虚拟机外，都继承了java.lang.ClassLoader。</li>
</ul>
<p>从Java开发者角度可以分为三类：</p>
<ul>
<li>启动类加载器（Bootstrap Class Loader）：这个类加载器负责加载存放在<JAVA_HOME>\lib目录，或者被-Xbootclasspath参数所指定的路径中存放的，而且是Java虚拟机能够识别的（按照文件名识别，如rt.jar、tools.jar，名字不符合的类库即使放在lib目录中也不会被加载）类库加载到虚拟机的内存中；</li>
<li>扩展类加载器（Extension Class Loader）：这个类加载器是在类sun.misc.Launcher$ExtClassLoader中以Java代码的形式实现的。它负责加载<JAVA_HOME>\lib\ext目录中，或者被java.ext.dirs系统变量所指定的路径中所有的类库。根据“扩展类加载器”这个名称，就可以推断出这是一种Java系统类库的扩展机制，JDK的开发团队允许用户将具有通用性的类库放置在ext目录里以扩展Java SE的功能；</li>
<li>应用程序类加载器（Application Class Loader）：这个类加载器由sun.misc.Launcher$AppClassLoader来实现。由于应用程序类加载器是ClassLoader类中的getSystem-ClassLoader()方法的返回值，所以有些场合中也称它为“系统类加载器”。它负责加载用户类路径（ClassPath）上所有的类库，开发者同样可以直接在代码中使用这个类加载器。如果应用程序中没有自定义过自己的类加载器，一般情况下这个就是程序中默认的类加载器。</li>
</ul>
<p>用户还可以自定义类加载器，如何实现自定义类加载器在后面会提到。</p>
<h2 id="双亲委派模型"><a href="#双亲委派模型" class="headerlink" title="双亲委派模型"></a>双亲委派模型</h2><p>各种类加载器之间的层次关系被称为类加载器的“双亲委派模型（Parents Delegation Model）”。</p>
<p>双亲委派模型的工作过程是：如果一个类加载器收到了类加载的请求，它首先不会自己去尝试加载这个类，而是把这个请求委派给父类加载器去完成，每一个层次的类加载器都是如此，因此所有的加载请求最终都应该传送到最顶层的启动类加载器中，只有当父加载器反馈自己无法完成这个加载请求（它的搜索范围中没有找到所需的类）时，子加载器才会尝试自己去完成加载。</p>
<p>下面是类的加载的实现代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">synchronized</span> Class&lt;?&gt; loadClass(String name, <span class="type">boolean</span> resolve) <span class="keyword">throws</span> ClassNotFoundException</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// 首先，检查请求的类是否已经被加载过了</span></span><br><span class="line">    <span class="type">Class</span> <span class="variable">c</span> <span class="operator">=</span> findLoadedClass(name);</span><br><span class="line">    <span class="keyword">if</span> (c == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (parent != <span class="literal">null</span>) &#123;</span><br><span class="line">            c = parent.loadClass(name, <span class="literal">false</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            c = findBootstrapClassOrNull(name);</span><br><span class="line">        &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">            <span class="comment">// 如果父类加载器抛出ClassNotFoundException</span></span><br><span class="line">            <span class="comment">// 说明父类加载器无法完成加载请求</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (c == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 在父类加载器无法加载时</span></span><br><span class="line">            <span class="comment">// 再调用本身的findClass方法来进行类加载</span></span><br><span class="line">            c = findClass(name);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (resolve) &#123;</span><br><span class="line">        resolveClass(c);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> c;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="破坏双亲委派模型"><a href="#破坏双亲委派模型" class="headerlink" title="破坏双亲委派模型"></a>破坏双亲委派模型</h2><p>双亲委派模型并不是一个具有强制性约束的模型，而是Java设计者推荐给开发者们的类加载器实现方式。既然双亲委派模型不是类加载的强制约束，那么必然存在不是以双亲委派模型来加载类的类加载方式，那这种加载方式就是在破坏双亲委派模型。历史上有三次破坏双亲委派模型的情况。</p>
<ul>
<li>第一次的出现是发生在双亲委派模型被引入的那个版本——JDK1.2，正是因为它是在JDK1.2才引入的，而类加载器的概念和抽象类java.lang.ClassLoader则在Java的第一个版本中就已经存在，面对已经存在的用户自定义类加载器的代码，Java设计者们引入双亲委派模型时不得不做出一些妥协，为了兼容这些已有代码，无法再以技术手段避免loadClass()被子类覆盖的可能性，只能在JDK 1.2之后的java.lang.ClassLoader中添加一个新的protected方法findClass()，并引导用户编写的类加载逻辑时尽可能去重写这个方法，而不是在loadClass()中编写代码。上节我们已经分析过loadClass()方法，双亲委派的具体逻辑就实现在这里面，按照loadClass()方法的逻辑，如果父类加载失败，会自动调用自己的findClass()方法来完成加载，这样既不影响用户按照自己的意愿去加载类，又可以保证新写出来的类加载器是符合双亲委派规则的。</li>
<li>第二次的出现是因为双亲委派模型本身的缺陷，双亲委派很好地解决了各个类加载器协作时基础类型的一致性问题（越基础的类由越上层的加载器进行加载），基础类型之所以被称为“基础”，是因为它们总是作为被用户代码继承、调用的API存在，但程序设计往往没有绝对不变的完美规则，如果有基础类型又要调用回用户的代码（这个就是SPI），那启动类加载器是绝不可能认识、加载这些代码的。所以，为了解决这个困境，Java的设计团队只好引入了一个不太优雅的设计：线程上下文类加载器（Thread Context ClassLoader）。这个类加载器可以通过java.lang.Thread类的setContext-ClassLoader()方法进行设置，如果创建线程时还未设置，它将会从父线程中继承一个，如果在应用程序的全局范围内都没有设置过的话，那这个类加载器默认就是应用程序类加载器。有了线程上下文类加载器，程序就可以做一些“舞弊”的事情了。JNDI服务使用这个线程上下文类加载器去加载所需的SPI服务代码，这是一种父类加载器去请求子类加载器完成类加载的行为，这种行为实际上是打通了双亲委派模型的层次结构来逆向使用类加载器，已经违背了双亲委派模型的一般性原则，但也是无可奈何的事情。Java中涉及SPI的加载基本上都采用这种方式来完成，例如JNDI、JDBC、JCE、JAXB和JBI等。不过，当SPI的服务提供者多于一个的时候，代码就只能根据具体提供者的类型来硬编码判断，为了消除这种极不优雅的实现方式，在JDK 6时，JDK提供了java.util.ServiceLoader类，以META-INF&#x2F;services中的配置信息，辅以责任链模式，这才算是给SPI的加载提供了一种相对合理的解决方案。</li>
<li>第三次出现是因为是由于用户对程序动态性的追求，为了实现热插拔，热部署，模块化，<strong>意思是添加一个功能或减去一个功能不用重启，只需要把这模块连同类加载器一起换掉就实现了代码的热替换</strong>。例如OSGi(Open Service Gateway Initiative)的出现。在OSGi环境下，类加载器不再是双亲委派模型中的树状结构，而是进一步发展为网状结构。</li>
</ul>
<h2 id="用户自定义类加载器"><a href="#用户自定义类加载器" class="headerlink" title="用户自定义类加载器"></a>用户自定义类加载器</h2><p>先写一个将要被手写类加载器加载的类</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> <span class="string">&quot;张三&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">age</span> <span class="operator">=</span> <span class="number">28</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">say</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;I am &quot;</span>+<span class="built_in">this</span>.name+<span class="string">&quot;,I was &quot;</span>+<span class="built_in">this</span>.age+<span class="string">&quot; years old.&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>再写一个自定义类加载器，继承ClassLoader类，重载findClass方法，在findClass中用自己定义的将要此类加载器要加载的 class文件转换成byte数组，再调用defineClass方法加载成一个Class对象，至此我们就用我们手写的类加载器加载了指定的class文件。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyClassLoader</span> <span class="keyword">extends</span> <span class="title class_">ClassLoader</span>&#123;</span><br><span class="line">    <span class="keyword">private</span> String classPath;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">MyClassLoader</span><span class="params">( String classPath)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.classPath = classPath;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">MyClassLoader</span> <span class="variable">loader</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MyClassLoader</span>(<span class="string">&quot;D:/code/ClassLoader-Test/target/classes/&quot;</span>);</span><br><span class="line">        <span class="type">Class</span> <span class="variable">clazz</span> <span class="operator">=</span> loader.findClass(<span class="string">&quot;com.xx.User&quot;</span>);</span><br><span class="line">        <span class="type">Object</span> <span class="variable">object</span> <span class="operator">=</span> clazz.newInstance();</span><br><span class="line">        <span class="type">Method</span> <span class="variable">method</span> <span class="operator">=</span> clazz.getDeclaredMethod(<span class="string">&quot;say&quot;</span>);</span><br><span class="line">        method.invoke(object);</span><br><span class="line">        System.out.println(clazz.getClassLoader().getClass().getName());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> Class&lt;?&gt; findClass(String name) <span class="keyword">throws</span> ClassNotFoundException &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">byte</span>[] bytes = loadByte(name);</span><br><span class="line">            <span class="keyword">return</span> defineClass(name,bytes,<span class="number">0</span>,bytes.length);</span><br><span class="line">        &#125;<span class="keyword">catch</span> (Exception e)&#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ClassNotFoundException</span>();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">byte</span>[] loadByte(String name) <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        name = name.replaceAll(<span class="string">&quot;\\.&quot;</span>, <span class="string">&quot;/&quot;</span>);</span><br><span class="line">        <span class="type">FileInputStream</span> <span class="variable">fis</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(classPath + <span class="string">&quot;/&quot;</span> + name</span><br><span class="line">            + <span class="string">&quot;.class&quot;</span>);</span><br><span class="line">        <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> fis.available();</span><br><span class="line">        <span class="type">byte</span>[] data = <span class="keyword">new</span> <span class="title class_">byte</span>[len];</span><br><span class="line">        fis.read(data);</span><br><span class="line">        fis.close();</span><br><span class="line">        <span class="keyword">return</span> data;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>


      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://xx1997.gitee.io/2022/04/28/Java%E7%BA%BF%E7%A8%8B%E6%B1%A0/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/xx1997/images/xxstar.jpg">
      <meta itemprop="name" content="xxstar">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="xxstar blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/xx1997/2022/04/28/Java%E7%BA%BF%E7%A8%8B%E6%B1%A0/" class="post-title-link" itemprop="url">Java线程池</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-04-28 14:36:49" itemprop="dateCreated datePublished" datetime="2022-04-28T14:36:49+08:00">2022-04-28</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-05-05 15:08:21" itemprop="dateModified" datetime="2022-05-05T15:08:21+08:00">2022-05-05</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/xx1997/categories/programming/" itemprop="url" rel="index"><span itemprop="name">编程</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>线程池是一种基于池化技术思想来管理线程的工具。在线程池中维护了多个线程，由线程池统一的管理调配线程来执行任务。通过线程复用，减少了频繁创建和销毁线程的开销。</p>
<h2 id="线程池的使用示例"><a href="#线程池的使用示例" class="headerlink" title="线程池的使用示例"></a>线程池的使用示例</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 实例化一个线程池</span></span><br><span class="line"><span class="type">ThreadPoolExecutor</span> <span class="variable">executor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(<span class="number">3</span>,<span class="number">10</span>, <span class="number">45</span>,TimeUnit.SECONDS, <span class="keyword">new</span> <span class="title class_">ArrayBlockingQueue</span>&lt;&gt;(<span class="number">10</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用线程池执行任务</span></span><br><span class="line">executor.execute(() -&gt; &#123;</span><br><span class="line">	<span class="comment">// 任务逻辑</span></span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// 关闭线程，不再接收新的任务，但会执行已经提交过的线程</span></span><br><span class="line">executor.shutdown();</span><br><span class="line"><span class="comment">// 关闭线程，不再接收新的任务，并且停止正在执行的线程</span></span><br><span class="line">executor.shutdownNow();</span><br></pre></td></tr></table></figure>

<p>由于ThreadPoolExecutor提供的构造方法参数过多，Java提供了一个线程工具类（Executors）来快速创建线程。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 实例化一个单线程的线程池</span></span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">singleThreadExecutor</span> <span class="operator">=</span> Executors.newSingleThreadExecutor();</span><br><span class="line"><span class="comment">// 实例化一个可重用固定线程的线程池</span></span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">cachedThreadPool</span> <span class="operator">=</span> Executors.newCachedThreadPool();</span><br><span class="line"><span class="comment">// 实例化一个固定线程数为10的线程池</span></span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">fixedThreadPool</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">10</span>);</span><br><span class="line"><span class="comment">// 实例化一个可在固定延迟时间或定期时间执行的线程池</span></span><br><span class="line"><span class="type">ScheduledExecutorService</span> <span class="variable">scheduledThreadPool</span> <span class="operator">=</span> Executors.newScheduledThreadPool(<span class="number">3</span>);</span><br><span class="line"><span class="comment">//延迟执行示例代码如下</span></span><br><span class="line"><span class="comment">//表示延迟5秒执行</span></span><br><span class="line">scheduledThreadPool.schedule(() -&gt; System.out.println(<span class="string">&quot;延迟5秒&quot;</span>), <span class="number">5</span>, TimeUnit.SECONDS);</span><br><span class="line"></span><br><span class="line"><span class="comment">//定期执行示例代码如下：</span></span><br><span class="line"><span class="comment">//表示延迟3秒后每2秒执行一次。</span></span><br><span class="line">scheduledThreadPool.scheduleAtFixedRate(() -&gt; System.out.println(<span class="string">&quot;延迟3秒，每隔2秒执行&quot;</span>), <span class="number">3</span>, <span class="number">2</span>, TimeUnit.SECONDS);</span><br></pre></td></tr></table></figure>

<h2 id="线程池的生命周期"><a href="#线程池的生命周期" class="headerlink" title="线程池的生命周期"></a>线程池的生命周期</h2><ul>
<li>RUNNING：运行状态，能够接收新提交的任务且能对提交的任务进行处理。但线程池一被创建就处于RUNNING状态；</li>
<li>SHUTDOWN：关闭状态，不接收新的任务，但是已经提交的任务可以处理。在运行状态的线程池调用shutdown()方法会进入关闭状态；</li>
<li>STOP：停止状态，不接收新任务，不处理已经提交的任务，并且中断正在执行的线程。运行状态的线程池调用shutdownNow()方法会进入到停止状态；</li>
<li>TIDYING：当所有任务都终止了，且任务数量为0时，线程池进入TIDYING状态。当线程池处于关闭状态时，阻塞队列中的任务都被执行完，且线程池中没有正在执行的任务，线程池进入TIDYING状态。当线程池处于停止状态，线程池没有正在执行的任务，则会进入到TIDYING状态；</li>
<li>TERMINATED：终止状态，处于TIDYING状态的线程池执行terminated()后进入TERMINATED状态。</li>
</ul>
<h2 id="ThreadPoolExecutor中的参数"><a href="#ThreadPoolExecutor中的参数" class="headerlink" title="ThreadPoolExecutor中的参数"></a>ThreadPoolExecutor中的参数</h2><p>ThreadPoolExecutor的构造方法源码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">ThreadPoolExecutor</span><span class="params">(<span class="type">int</span> corePoolSize,</span></span><br><span class="line"><span class="params">                          <span class="type">int</span> maximumPoolSize,</span></span><br><span class="line"><span class="params">                          <span class="type">long</span> keepAliveTime,</span></span><br><span class="line"><span class="params">                          TimeUnit unit,</span></span><br><span class="line"><span class="params">                          BlockingQueue&lt;Runnable&gt; workQueue,</span></span><br><span class="line"><span class="params">                          ThreadFactory threadFactory,</span></span><br><span class="line"><span class="params">                          RejectedExecutionHandler handler)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (corePoolSize &lt; <span class="number">0</span> ||</span><br><span class="line">        maximumPoolSize &lt;= <span class="number">0</span> ||</span><br><span class="line">        maximumPoolSize &lt; corePoolSize ||</span><br><span class="line">        keepAliveTime &lt; <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>();</span><br><span class="line">    <span class="keyword">if</span> (workQueue == <span class="literal">null</span> || threadFactory == <span class="literal">null</span> || handler == <span class="literal">null</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line">    <span class="built_in">this</span>.acc = System.getSecurityManager() == <span class="literal">null</span> ? <span class="literal">null</span> : AccessController.getContext();</span><br><span class="line">    <span class="built_in">this</span>.corePoolSize = corePoolSize;</span><br><span class="line">    <span class="built_in">this</span>.maximumPoolSize = maximumPoolSize;</span><br><span class="line">    <span class="built_in">this</span>.workQueue = workQueue;</span><br><span class="line">    <span class="built_in">this</span>.keepAliveTime = unit.toNanos(keepAliveTime);</span><br><span class="line">    <span class="built_in">this</span>.threadFactory = threadFactory;</span><br><span class="line">    <span class="built_in">this</span>.handler = handler;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ol>
<li>corePoolSize：核心线程数量，如果线程池收到任务，且线程池内部线程数量没有达到corePoolSize，线程池会直接给此任务创建一个新线程来处理此任务。当线程池中达到该值，池中要保留的线程数，即使它们处于空闲状态。</li>
<li>maximumPoolSize：池中允许的最大线程数。</li>
<li>keepAliveTime：当线程数大于核心时，这是参数的值为多余空闲线程在终止前等待新任务的最长时间。</li>
<li>unit：keepAliveTime参数的时间单位。</li>
<li>workQueue：用于在任务执行前保存任务的队列，通常有固定数量的ArrayBlockingQueue，无限制的LinkedBlockingQueue。此队列将仅包含execute方法提交的可运行任务。</li>
<li>threadFactory：线程池创建新线程时使用的工厂。</li>
<li>handler：任务拒绝策略， 当任务队列已满，又有新的任务进来时，会回调此接口。</li>
</ol>
<p>线程池使用了一个AtomicInteger类参数通知表示线程池状态和线程池线程数量</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 原子int类</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">ctl</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(ctlOf(RUNNING, <span class="number">0</span>));</span><br><span class="line"><span class="comment">// int类是32位，所以COUNT_BITS=32-3=29</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">COUNT_BITS</span> <span class="operator">=</span> Integer.SIZE - <span class="number">3</span>;</span><br><span class="line"><span class="comment">// 00011111 11111111 11111111 11111111表示线程的最大值</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">CAPACITY</span>   <span class="operator">=</span> (<span class="number">1</span> &lt;&lt; COUNT_BITS) - <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// -1在计算机中以补码的形式储存即11111111 11111111 11111111 11111111</span></span><br><span class="line"><span class="comment">// 故RUNNING=11100000 00000000 00000000 00000000</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">RUNNING</span>    <span class="operator">=</span> -<span class="number">1</span> &lt;&lt; COUNT_BITS;</span><br><span class="line"><span class="comment">// SHUTDOWN=00000000 00000000 00000000 00000000</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">SHUTDOWN</span>   <span class="operator">=</span>  <span class="number">0</span> &lt;&lt; COUNT_BITS;</span><br><span class="line"><span class="comment">// STOP=00100000 00000000 00000000 00000000</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">STOP</span>       <span class="operator">=</span>  <span class="number">1</span> &lt;&lt; COUNT_BITS;</span><br><span class="line"><span class="comment">// TIDYING=01000000 00000000 00000000 00000000</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">TIDYING</span>    <span class="operator">=</span>  <span class="number">2</span> &lt;&lt; COUNT_BITS;</span><br><span class="line"><span class="comment">// TERMINATED=01100000 00000000 00000000 00000000</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">TERMINATED</span> <span class="operator">=</span>  <span class="number">3</span> &lt;&lt; COUNT_BITS;</span><br><span class="line"></span><br><span class="line"><span class="comment">//~CAPACITY表示取CAPACITY的反码，即11100000 000000000 000000000 000000000</span></span><br><span class="line"><span class="comment">//再与c做“且”运算，那么运算出的结果后29位必定是0，最高三位就是c的最高三位，得到的就是当前线程的状态</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">runStateOf</span><span class="params">(<span class="type">int</span> c)</span>     &#123; <span class="keyword">return</span> c &amp; ~CAPACITY; &#125;</span><br><span class="line"><span class="comment">//c直接与CAPACITY做“且”运算，那么运算出的结果最高3位必定是0，剩余29位就是c的剩余29位，得到的就是当前线程池的线程数量</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">workerCountOf</span><span class="params">(<span class="type">int</span> c)</span>  &#123; <span class="keyword">return</span> c &amp; CAPACITY; &#125;</span><br></pre></td></tr></table></figure>

<p>线程用一个原子int类的参数ctl同时表达了线程池线程数量和线程池当前状态，参数的前三位表示线程池状态，后29位表示线程池线程数量，通过位运算可以非常快地计算出线程池的线程数量和线程池的当前状态。</p>
<h2 id="线程池的工作流程"><a href="#线程池的工作流程" class="headerlink" title="线程池的工作流程"></a>线程池的工作流程</h2><p>想要了解清楚线程池的工作流程，可以通过分析线程池的execute()方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">execute</span><span class="params">(Runnable command)</span> &#123;</span><br><span class="line">    <span class="comment">// 判断任务是否为空，是空抛出异常</span></span><br><span class="line">    <span class="keyword">if</span> (command == <span class="literal">null</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line">    <span class="comment">// 获取当前ctl的数值</span></span><br><span class="line">	<span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> ctl.get();</span><br><span class="line">    <span class="keyword">if</span> (workerCountOf(c) &lt; corePoolSize) &#123;</span><br><span class="line">        <span class="comment">// 如果当前线程数量小于核心线程数</span></span><br><span class="line">        <span class="comment">// 尝试添加新的核心线程执行该任务</span></span><br><span class="line">        <span class="keyword">if</span> (addWorker(command, <span class="literal">true</span>))</span><br><span class="line">            <span class="comment">// 添加新线程成功，直接返回</span></span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        <span class="comment">// 失败，重新获取ctl数值</span></span><br><span class="line">        c = ctl.get();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 判断当前线程池状态是否是运行状态，且尝试将任务放入等待队列中</span></span><br><span class="line">    <span class="keyword">if</span> (isRunning(c) &amp;&amp; workQueue.offer(command)) &#123;</span><br><span class="line">        <span class="comment">// 重新获取ctl的数值，用来做确认</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">recheck</span> <span class="operator">=</span> ctl.get();</span><br><span class="line">        <span class="keyword">if</span> (! isRunning(recheck) &amp;&amp; remove(command))</span><br><span class="line">            <span class="comment">// 如果再次确认线程池状态时，线程池状态已不是运行状态，且能成功将任务从等待队列移除</span></span><br><span class="line">            <span class="comment">// 使用拒绝策略拒绝此任务</span></span><br><span class="line">            reject(command);</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (workerCountOf(recheck) == <span class="number">0</span>)</span><br><span class="line">            <span class="comment">// 如果上一个判断条件不满足，且当前线程池线程数量为0</span></span><br><span class="line">            <span class="comment">// 新添加一个非核心线程执行此任务</span></span><br><span class="line">            addWorker(<span class="literal">null</span>, <span class="literal">false</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果线程池状态为非运行状态，或者将任务放入等待队列失败</span></span><br><span class="line">    <span class="comment">// 尝试创建非核心进程执行此任务</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (!addWorker(command, <span class="literal">false</span>))</span><br><span class="line">        <span class="comment">// 创建非核心进程失败，使用拒绝策略拒绝此任务</span></span><br><span class="line">        reject(command);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>execute()执行主流程就如上面代码所示，还有几点：</p>
<ol>
<li>如果线程池中线程执行完任，会从等待队列获取第一个任务来执行，如果等待队列为空，则线程池线程会被阻塞；</li>
<li>如果线程池线程数量超过了核心线程数值，那么线程经过keepAliveTime会被销毁，但核心线程会被保留，不过，如果allowCoreThreadTimeOut设置为true，核心线程空闲超过keepAliveTime也会被销毁。</li>
</ol>
<h2 id="线程池拒绝策略"><a href="#线程池拒绝策略" class="headerlink" title="线程池拒绝策略"></a>线程池拒绝策略</h2><p>如果线程池的线程数量已经达到maximumPoolSize，并且等待队列workQueue已经存储满了的情况下，线程池会执行拒绝策略拒绝新的任务。JDK提供了RejectedExecutionHandler接口；来执行拒绝操作，实现RejectedExecutionHandler接口的类有四个，对应了四种拒绝策略。</p>
<ul>
<li><strong>DiscardPolicy</strong>：当提交任务到线程池被拒绝时，线程池会放弃这个任务；</li>
<li><strong>DiscardOldestPolicy</strong>：当提交任务到线程池中被拒绝时，线程池会丢弃在线程等到最旧的任务；</li>
<li><strong>CallerRunsPolicy</strong>：当任务提交到线程池被拒绝后，直接在execute方法的调用线程中运行被拒绝的任务，即谁提交的线程谁执行任务；</li>
<li><strong>AbortPolicy</strong>：当任务提交到线程池被拒绝后，直接抛出RejectedExecutionException异常。</li>
</ul>
<h2 id="源码常用方法分析"><a href="#源码常用方法分析" class="headerlink" title="源码常用方法分析"></a>源码常用方法分析</h2><ul>
<li><strong>addWorker</strong></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">addWorker</span><span class="params">(Runnable firstTask, <span class="type">boolean</span> core)</span> &#123;</span><br><span class="line">    <span class="comment">// retry标记，功能类似goto</span></span><br><span class="line">    retry:</span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        <span class="comment">// 获取ctl数值</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> ctl.get();</span><br><span class="line">        <span class="comment">// 获取线程池当前状态数值</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">rs</span> <span class="operator">=</span> runStateOf(c);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Check if queue empty only if necessary.</span></span><br><span class="line">        <span class="keyword">if</span> (rs &gt;= SHUTDOWN &amp;&amp;</span><br><span class="line">            ! (rs == SHUTDOWN &amp;&amp;</span><br><span class="line">               firstTask == <span class="literal">null</span> &amp;&amp;</span><br><span class="line">               ! workQueue.isEmpty()))</span><br><span class="line">            <span class="comment">// 线程池为非RUNNING状态和非SHUTDOWN状态</span></span><br><span class="line">            <span class="comment">// 当线程池为SHUTDOWN状态时，且任务为空、队列不为空，此时如果线程池没有线程，但为了处理队列原先已经提交的任务，需要创建新的线程</span></span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">            <span class="comment">// 取得当前线程池线程数量</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">wc</span> <span class="operator">=</span> workerCountOf(c);</span><br><span class="line">            <span class="keyword">if</span> (wc &gt;= CAPACITY ||</span><br><span class="line">                 wc &gt;= (core ? corePoolSize : maximumPoolSize))</span><br><span class="line">                <span class="comment">// 当前值达到最大线程最大容量</span></span><br><span class="line">                <span class="comment">// 根据core值判断当前创建的线程是核心线程还是非核心线程，进而判断用corePoolSize、maximumPoolSize校验当前是否达到阀值</span></span><br><span class="line">               <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            <span class="keyword">if</span> (compareAndIncrementWorkerCount(c))</span><br><span class="line">                <span class="comment">// 用CAS增加线程数量，成功则跳出retry（跳出最外层for循环，执行下面逻辑）</span></span><br><span class="line">                <span class="keyword">break</span> retry;</span><br><span class="line">            c = ctl.get();  <span class="comment">// Re-read ctl</span></span><br><span class="line">            <span class="keyword">if</span> (runStateOf(c) != rs)</span><br><span class="line">                <span class="comment">// 当前线程池状态已经改变，回到retry，重新执行for循环</span></span><br><span class="line">                <span class="keyword">continue</span> retry;</span><br><span class="line">            <span class="comment">// else CAS failed due to workerCount change; retry inner loop</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">workerStarted</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">workerAdded</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="type">Worker</span> <span class="variable">w</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 创建Worker实例，内部会创建线程</span></span><br><span class="line">        w = <span class="keyword">new</span> <span class="title class_">Worker</span>(firstTask);</span><br><span class="line">        <span class="comment">// 获取新创建Worker实例创建的线程</span></span><br><span class="line">        <span class="keyword">final</span> <span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> w.thread;</span><br><span class="line">        <span class="keyword">if</span> (t != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 使用ReentrantLock锁加锁保证线程安全</span></span><br><span class="line">            <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">mainLock</span> <span class="operator">=</span> <span class="built_in">this</span>.mainLock;</span><br><span class="line">            mainLock.lock();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// Recheck while holding lock.</span></span><br><span class="line">                <span class="comment">// Back out on ThreadFactory failure or if</span></span><br><span class="line">                <span class="comment">// shut down before lock acquired.</span></span><br><span class="line">                <span class="type">int</span> <span class="variable">rs</span> <span class="operator">=</span> runStateOf(ctl.get());</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (rs &lt; SHUTDOWN ||</span><br><span class="line">                    (rs == SHUTDOWN &amp;&amp; firstTask == <span class="literal">null</span>)) &#123;</span><br><span class="line">                    <span class="comment">//线程池状态是RUNNING状态或者线程池状态是SHUTDOWN且任务为空</span></span><br><span class="line">                    <span class="keyword">if</span> (t.isAlive()) <span class="comment">// precheck that t is startable</span></span><br><span class="line">                        <span class="comment">// 判断新创建的线程是否还存活，不存活就抛出异常</span></span><br><span class="line">                        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalThreadStateException</span>();</span><br><span class="line">                    <span class="comment">// 将线程加入到线程池，workers是HashSet</span></span><br><span class="line">                    workers.add(w);</span><br><span class="line">                    <span class="type">int</span> <span class="variable">s</span> <span class="operator">=</span> workers.size();</span><br><span class="line">                    <span class="keyword">if</span> (s &gt; largestPoolSize)</span><br><span class="line">                        <span class="comment">// largestPoolSize是线程池大小，且只能在加锁的情况下访问</span></span><br><span class="line">                        <span class="comment">// 这里重新赋值了largestPoolSize</span></span><br><span class="line">                        largestPoolSize = s;</span><br><span class="line">                    workerAdded = <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                mainLock.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (workerAdded) &#123;</span><br><span class="line">                <span class="comment">// 线程添加成功，就启动该线程</span></span><br><span class="line">                t.start();</span><br><span class="line">                workerStarted = <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (! workerStarted)</span><br><span class="line">            addWorkerFailed(w);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> workerStarted;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>看addWorker()方法的源码可以发现该方法可以分为retry部分和addWorker部分，retry部分做是否能新增线程的校验，addWorker才是真正添加线程的逻辑。从源码可以看出线程真正地创建是在Worker类实现的，其实Worker类不单单只是创建线程那么简单，它继承了AbstractQueuedSynchronizer（AQS）类，通过AQS来实现独占锁，下面分析一下Worker类：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Worker是ThreadPoolExecutor的内部类</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">Worker</span> <span class="keyword">extends</span> <span class="title class_">AbstractQueuedSynchronizer</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">serialVersionUID</span> <span class="operator">=</span> <span class="number">6138294804551838833L</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 执行任务的线程</span></span><br><span class="line">    <span class="keyword">final</span> Thread thread;</span><br><span class="line">    <span class="comment">// 初始化Worker时传进来的任务，有可能为null，如果不为空则立刻执行这个任务</span></span><br><span class="line">    Runnable firstTask;</span><br><span class="line">    <span class="comment">// 执行任务的数量</span></span><br><span class="line">    <span class="keyword">volatile</span> <span class="type">long</span> completedTasks;</span><br><span class="line"></span><br><span class="line">    Worker(Runnable firstTask) &#123;</span><br><span class="line">        <span class="comment">// 初始化state为-1</span></span><br><span class="line">        setState(-<span class="number">1</span>);</span><br><span class="line">        <span class="built_in">this</span>.firstTask = firstTask;</span><br><span class="line">        <span class="comment">// 通过线程工厂创建线程</span></span><br><span class="line">        <span class="built_in">this</span>.thread = getThreadFactory().newThread(<span class="built_in">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 线程真正执行的方法</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        runWorker(<span class="built_in">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 判断线程是否已经被占用，0表示未被占用，1表示被占用</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">isHeldExclusively</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> getState() != <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取锁（重写AbstractQueuedSynchronizer的tryAcquire方法）</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryAcquire</span><span class="params">(<span class="type">int</span> unused)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, <span class="number">1</span>)) &#123;</span><br><span class="line">            setExclusiveOwnerThread(Thread.currentThread());</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 释放锁（重写AbstractQueuedSynchronizer的tryRelease方法）</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryRelease</span><span class="params">(<span class="type">int</span> unused)</span> &#123;</span><br><span class="line">        setExclusiveOwnerThread(<span class="literal">null</span>);</span><br><span class="line">        setState(<span class="number">0</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">lock</span><span class="params">()</span>        &#123; acquire(<span class="number">1</span>); &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">tryLock</span><span class="params">()</span>  &#123; <span class="keyword">return</span> tryAcquire(<span class="number">1</span>); &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">()</span>      &#123; release(<span class="number">1</span>); &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isLocked</span><span class="params">()</span> &#123; <span class="keyword">return</span> isHeldExclusively(); &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">interruptIfStarted</span><span class="params">()</span> &#123;</span><br><span class="line">        Thread t;</span><br><span class="line">        <span class="keyword">if</span> (getState() &gt;= <span class="number">0</span> &amp;&amp; (t = thread) != <span class="literal">null</span> &amp;&amp; !t.isInterrupted()) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                t.interrupt();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (SecurityException ignore) &#123;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>真正实现线程运行是是runWorker方法，runWorker方法通过不多从等待队列获取任务执行从而实现线程的不断运行，使用了Worker类的lock和unlock方法来实现线程的独占，下面是runWorker方法的源码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">runWorker</span><span class="params">(Worker w)</span> &#123;</span><br><span class="line">    <span class="type">Thread</span> <span class="variable">wt</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">    <span class="comment">// 获取Worker中的任务</span></span><br><span class="line">    <span class="type">Runnable</span> <span class="variable">task</span> <span class="operator">=</span> w.firstTask;</span><br><span class="line">    w.firstTask = <span class="literal">null</span>;</span><br><span class="line">    w.unlock(); <span class="comment">// allow interrupts</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">completedAbruptly</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 如果task不为空，或者从等待队列获取任务不为空，一直循环从等待队列获取任务</span></span><br><span class="line">        <span class="keyword">while</span> (task != <span class="literal">null</span> || (task = getTask()) != <span class="literal">null</span>) &#123;</span><br><span class="line">            w.lock();</span><br><span class="line">            <span class="comment">// 如果池正在停止，确保线程被中断；</span></span><br><span class="line">            <span class="comment">// 如果没有，确保线程没有中断。</span></span><br><span class="line">            <span class="comment">// 这在第二种情况下需要重新检查才能处理在清除中断的同时停止执行</span></span><br><span class="line">            <span class="keyword">if</span> ((runStateAtLeast(ctl.get(), STOP) ||</span><br><span class="line">                 (Thread.interrupted() &amp;&amp;</span><br><span class="line">                  runStateAtLeast(ctl.get(), STOP))) &amp;&amp;</span><br><span class="line">                !wt.isInterrupted())</span><br><span class="line">                wt.interrupt();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 任务执行前的hook点</span></span><br><span class="line">                beforeExecute(wt, task);</span><br><span class="line">                <span class="type">Throwable</span> <span class="variable">thrown</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="comment">// 执行任务</span></span><br><span class="line">                    task.run();</span><br><span class="line">                &#125; <span class="keyword">catch</span> (RuntimeException x) &#123;</span><br><span class="line">                    thrown = x; <span class="keyword">throw</span> x;</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Error x) &#123;</span><br><span class="line">                    thrown = x; <span class="keyword">throw</span> x;</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Throwable x) &#123;</span><br><span class="line">                    thrown = x; <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(x);</span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    <span class="comment">// 任务执行后的hook点</span></span><br><span class="line">                    afterExecute(task, thrown);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                task = <span class="literal">null</span>;</span><br><span class="line">                w.completedTasks++;</span><br><span class="line">                w.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        completedAbruptly = <span class="literal">false</span>;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="comment">// 超时没有获取到任务，则在空闲超时后回收线程</span></span><br><span class="line">        processWorkerExit(w, completedAbruptly);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>获取队列调用的是getTask方法，getTask方法不单单只是获取任务，因为线程为非核心线程或者allowCoreThreadTimeOut等于true时，线程是会因为空闲时间过长而被销毁的，所以当getTask方法达到一定的超时条件时还没获取到任务，调用该getTask方法获取任务的线程就达到了销毁条件。下面是getTask的源码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 从等待队列获取任务方法</span></span><br><span class="line"><span class="keyword">private</span> Runnable <span class="title function_">getTask</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">timedOut</span> <span class="operator">=</span> <span class="literal">false</span>; <span class="comment">// Did the last poll() time out?</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> ctl.get();</span><br><span class="line">        <span class="type">int</span> <span class="variable">rs</span> <span class="operator">=</span> runStateOf(c);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 线程池状态是SHUTDOWN且等待队列为空，或者线程池状态是STOP、TIDYING、TERMINATED</span></span><br><span class="line">        <span class="keyword">if</span> (rs &gt;= SHUTDOWN &amp;&amp; (rs &gt;= STOP || workQueue.isEmpty())) &#123;</span><br><span class="line">            <span class="comment">// 减少线程池线程数量</span></span><br><span class="line">            decrementWorkerCount();</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取当前线程池线程数量</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">wc</span> <span class="operator">=</span> workerCountOf(c);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// allowCoreThreadTimeOut是否为true，或者线程池线程数量是否大于核心线程数值</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">timed</span> <span class="operator">=</span> allowCoreThreadTimeOut || wc &gt; corePoolSize;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> ((wc &gt; maximumPoolSize || (timed &amp;&amp; timedOut))</span><br><span class="line">            &amp;&amp; (wc &gt; <span class="number">1</span> || workQueue.isEmpty())) &#123;</span><br><span class="line">            <span class="comment">// 线程数大于核心线程值或者过期标志为true且已达到过期条件，而且线程数大于1或者等待队列为空</span></span><br><span class="line">            <span class="keyword">if</span> (compareAndDecrementWorkerCount(c))</span><br><span class="line">                <span class="comment">// 线程数值减1</span></span><br><span class="line">                <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 过期标志为true，通过poll从等待队列获取任务，</span></span><br><span class="line">            <span class="comment">// 如果keepAliveTime后没有获取到任务，就返回空</span></span><br><span class="line">            <span class="comment">// 过期标志为false，就通过take方法获取任务</span></span><br><span class="line">            <span class="type">Runnable</span> <span class="variable">r</span> <span class="operator">=</span> timed ?</span><br><span class="line">                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :</span><br><span class="line">                workQueue.take();</span><br><span class="line">            <span class="keyword">if</span> (r != <span class="literal">null</span>)</span><br><span class="line">                <span class="keyword">return</span> r;</span><br><span class="line">            timedOut = <span class="literal">true</span>;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException retry) &#123;</span><br><span class="line">            timedOut = <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>shutdown方法和shutdownNow方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">shutdown</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">mainLock</span> <span class="operator">=</span> <span class="built_in">this</span>.mainLock;</span><br><span class="line">    mainLock.lock();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 检查是否可以关闭线程</span></span><br><span class="line">        checkShutdownAccess();</span><br><span class="line">        <span class="comment">// 将线程池状态改为SHUTDOWN</span></span><br><span class="line">        advanceRunState(SHUTDOWN);</span><br><span class="line">        <span class="comment">// 中断空闲线程</span></span><br><span class="line">        interruptIdleWorkers();</span><br><span class="line">        <span class="comment">// 线程池关闭的hook点</span></span><br><span class="line">        onShutdown(); <span class="comment">// hook for ScheduledThreadPoolExecutor</span></span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        mainLock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">    tryTerminate();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> List&lt;Runnable&gt; <span class="title function_">shutdownNow</span><span class="params">()</span> &#123;</span><br><span class="line">    List&lt;Runnable&gt; tasks;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">mainLock</span> <span class="operator">=</span> <span class="built_in">this</span>.mainLock;</span><br><span class="line">    mainLock.lock();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        checkShutdownAccess();</span><br><span class="line">        advanceRunState(STOP);</span><br><span class="line">        interruptWorkers();</span><br><span class="line">        <span class="comment">// 获取等待队列还未执行的任务</span></span><br><span class="line">        tasks = drainQueue();</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        mainLock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">    tryTerminate();</span><br><span class="line">    <span class="keyword">return</span> tasks;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://xx1997.gitee.io/2022/04/22/SQL%E8%AF%AD%E6%B3%95%EF%BC%88%E5%88%9D%E9%98%B6%EF%BC%89/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/xx1997/images/xxstar.jpg">
      <meta itemprop="name" content="xxstar">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="xxstar blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/xx1997/2022/04/22/SQL%E8%AF%AD%E6%B3%95%EF%BC%88%E5%88%9D%E9%98%B6%EF%BC%89/" class="post-title-link" itemprop="url">未命名</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-04-22 14:09:51" itemprop="dateCreated datePublished" datetime="2022-04-22T14:09:51+08:00">2022-04-22</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-04-24 09:05:59" itemprop="dateModified" datetime="2022-04-24T09:05:59+08:00">2022-04-24</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>结构化查询语言（Structured Query Language）简称SQL，是一种特殊目的的编程语言，是一种数据库查询和程序设计语言，用于存取数据以及查询、更新和管理关系数据库系统。</p>
<h2 id="SQL-语法结构"><a href="#SQL-语法结构" class="headerlink" title="SQL 语法结构"></a>SQL 语法结构</h2><ul>
<li><p>**<code>子句</code>**：是语句和查询的组成成分。（在某些情况下，这些都是可选的。）</p>
</li>
<li><p>**<code>表达式</code>**：可以产生任何标量值，或由列和行的数据库表</p>
</li>
<li><p>**<code>谓词</code>**：给需要评估的 SQL 三值逻辑（3VL）（true&#x2F;false&#x2F;unknown）或布尔真值指定条件，并限制语句和查询的效果，或改变程序流程。</p>
</li>
<li><p>**<code>查询</code>**：基于特定条件检索数据。这是 SQL 的一个重要组成部分。</p>
</li>
<li><p>**<code>语句</code>**：可以持久地影响纲要和数据，也可以控制数据库事务、程序流程、连接、会话或诊断。</p>
</li>
</ul>
<h2 id="SQl分类"><a href="#SQl分类" class="headerlink" title="SQl分类"></a>SQl分类</h2><h3 id="数据定义语言（DDL）"><a href="#数据定义语言（DDL）" class="headerlink" title="数据定义语言（DDL）"></a>数据定义语言（DDL）</h3><p>其语句包括动词CREATE,ALTER和DROP。在数据库中创建新表或修改、删除表（CREATE TABLE 或 DROP TABLE）；为表加入索引等。</p>
<h3 id="数据查询语言（DQL-Data-Query-Language）"><a href="#数据查询语言（DQL-Data-Query-Language）" class="headerlink" title="数据查询语言（DQL:Data Query Language）"></a>数据查询语言（DQL:Data Query Language）</h3><p>也称为“数据检索语句”，用以从表中获得数据，确定数据怎样在应用程序给出。保留字SELECT是DQL（也是所有SQL）用得最多的动词，其他DQL常用的保留字有WHERE，ORDER BY，GROUP BY和HAVING。这些DQL保留字常与其它类型的SQL语句一起使用。</p>
<h3 id="数据操作语言（DML-Data-Manipulation-Language）"><a href="#数据操作语言（DML-Data-Manipulation-Language）" class="headerlink" title="数据操作语言（DML:Data Manipulation Language）"></a>数据操作语言（DML:Data Manipulation Language）</h3><p>其语句包括动词INSERT、UPDATE和DELETE。它们分别用于添加、修改和删除。</p>
<h3 id="事务控制语言（TCL）"><a href="#事务控制语言（TCL）" class="headerlink" title="事务控制语言（TCL）"></a>事务控制语言（TCL）</h3><p>它的语句能确保被DML语句影响的表的所有行及时得以更新。包括COMMIT（提交）命令、SAVEPOINT（保存点）命令、ROLLBACK（回滚）命令。</p>
<h3 id="数据控制语言（DCL）"><a href="#数据控制语言（DCL）" class="headerlink" title="数据控制语言（DCL）"></a>数据控制语言（DCL）</h3><p>它的语句通过GRANT或REVOKE实现权限控制，确定单个用户和用户组对数据库对象的访问。某些RDBMS可用GRANT或REVOKE控制对表单个列的访问。</p>
<h3 id="指针控制语言（CCL）"><a href="#指针控制语言（CCL）" class="headerlink" title="指针控制语言（CCL）"></a>指针控制语言（CCL）</h3><p>它的语句，像DECLARE CURSOR，FETCH INTO和UPDATE WHERE CURRENT用于对一个或多个表单独行的操作。</p>
<h2 id="数据库"><a href="#数据库" class="headerlink" title="数据库"></a>数据库</h2><ul>
<li>创建数据库：create database 数据库名 [其他选项]，如：create database student character set utf8; ，创建名为student的数据库，并设置数据库编码格式为UTF-8。</li>
<li>删除数据库：drop database 数据库名，如drop database student;。</li>
<li>显示数据库：show databases 数据库名。</li>
<li>选择数据库：use 数据库名，如use student;。</li>
</ul>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://xx1997.gitee.io/2022/04/22/MySql%E5%9F%BA%E7%A1%80%E6%9E%B6%E6%9E%84/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/xx1997/images/xxstar.jpg">
      <meta itemprop="name" content="xxstar">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="xxstar blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/xx1997/2022/04/22/MySql%E5%9F%BA%E7%A1%80%E6%9E%B6%E6%9E%84/" class="post-title-link" itemprop="url">MySql基础架构</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
              

              <time title="创建时间：2022-04-22 10:54:02 / 修改时间：13:38:59" itemprop="dateCreated datePublished" datetime="2022-04-22T10:54:02+08:00">2022-04-22</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/xx1997/categories/programming/" itemprop="url" rel="index"><span itemprop="name">编程</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <p><img src="https://static001.geekbang.org/resource/image/0d/d9/0d2070e8f84c4801adbfa03bda1f98d9.png"></p>
<ul>
<li><p>连接器</p>
<p>连接器负责跟客户端建立连接、获取权限、维持和管理连接。show processlist命令可以查看当前所有连接。<br>客户端如果太长时间没动静，连接器就会自动将它断开。这个时间是由参数wait_timeout控制的，默认值是8小时。<br>建立连接的过程比较复杂，所以尽量使用长连接，但是使用长连接内存会涨的很快，</p>
<ol>
<li>定期断开长连接。使用一段时间，或者程序里面判断执行过一个占用内存的大查询后，断开连接，之后要查询再重连。</li>
<li>如果你用的是MySQL 5.7或更新版本，可以在每次执行一个比较大的操作后，通过执行 mysql_reset_connection来重新初始化连接资源。这个过程不需要重连和重新做权限验证，但是会将连接恢复到刚刚创建完时的状态</li>
</ol>
</li>
<li><p>查询缓存<br>MySQL拿到一个查询请求后，会先到查询缓存看看，之前是不是执行过这条语句。之前执行过的语句及其结果可能会以key-value对的形式，被直接缓存在内存中。key是查询的语句，value是查询的结果。如果你的查询能够直接在这个缓存中找到key，那么这个value就会被直接返回给客户端。好在MySQL也提供了这种“按需使用”的方式。你可以将参数query_cache_type设置成DEMAND，这样对于默认的SQL语句都不使用查询缓存。而对于你确定要使用查询缓存的语句，可以用SQL_CACHE显式指定，像下面这个语句一样：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; select SQL_CACHE * from T where ID=10；</span><br></pre></td></tr></table></figure>
</li>
<li><p>分析器<br>分析器先会做“词法分析”。你输入的是由多个字符串和空格组成的一条SQL语句，MySQL需要识别出里面的字符串分别是什么，代表什么。</p>
<p>MySQL从你输入的”select”这个关键字识别出来，这是一个查询语句。它也要把字符串“T”识别成“表名T”，把字符串“ID”识别成“列ID”。</p>
<p>做完了这些识别以后，就要做“语法分析”。根据词法分析的结果，语法分析器会根据语法规则，判断你输入的这个SQL语句是否满足MySQL语法。</p>
</li>
<li><p>优化器<br>优化器是在表里面有多个索引的时候，决定使用哪个索引；或者在一个语句有多表关联（join）的时候，决定各个表的连接顺序。</p>
</li>
<li><p>执行器</p>
<p>先判断一下你对这个表T有没有执行查询的权限，如果有权限，就打开表继续执行。打开表的时候，执行器就会根据表的引擎定义，去使用这个引擎提供的接口。</p>
</li>
</ul>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://xx1997.gitee.io/2022/04/21/Linux%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/xx1997/images/xxstar.jpg">
      <meta itemprop="name" content="xxstar">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="xxstar blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/xx1997/2022/04/21/Linux%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/" class="post-title-link" itemprop="url">Linux常用命令</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-04-21 10:50:00" itemprop="dateCreated datePublished" datetime="2022-04-21T10:50:00+08:00">2022-04-21</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-04-28 16:04:09" itemprop="dateModified" datetime="2022-04-28T16:04:09+08:00">2022-04-28</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/xx1997/categories/programming/" itemprop="url" rel="index"><span itemprop="name">编程</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <h2 id="Linux命令"><a href="#Linux命令" class="headerlink" title="Linux命令"></a>Linux命令</h2><h3 id="1-echo命令"><a href="#1-echo命令" class="headerlink" title="1. echo命令"></a>1. echo命令</h3><p>将hello，world输出到终端屏幕</p>
<p>echo hello，world</p>
<p>输出变量</p>
<p>echo $变量名称</p>
<h3 id="2-date命令"><a href="#2-date命令" class="headerlink" title="2. date命令"></a>2. date命令</h3><p>date输出日期</p>
<p>date -s “2021-12-14 10:00:00”更改时间为2021-12-14 10:00:00</p>
<h3 id="3-reboot命令"><a href="#3-reboot命令" class="headerlink" title="3. reboot命令"></a>3. reboot命令</h3><p>重启</p>
<h3 id="4-poweroff命令"><a href="#4-poweroff命令" class="headerlink" title="4. poweroff命令"></a>4. poweroff命令</h3><p>关机</p>
<h3 id="5-wget命令"><a href="#5-wget命令" class="headerlink" title="5. wget命令"></a>5. wget命令</h3><p>wget <a target="_blank" rel="noopener" href="http://www.linuxprobe.com/docs/LinuxProbe.pdf">http://www.linuxprobe.com/docs/LinuxProbe.pdf</a> 下载文件</p>
<p>wget -r -p <a target="_blank" rel="noopener" href="http://www.linuxprobe.com递归下载www.linuxprobe.com/">http://www.linuxprobe.com递归下载www.linuxprobe.com</a> 网站内的所有页面数据以及文件，下载完后会自动保存到当前路径下一个名为<a target="_blank" rel="noopener" href="http://www.linuxprobe.com的目录中/">www.linuxprobe.com的目录中</a></p>
<h3 id="6-ps命令"><a href="#6-ps命令" class="headerlink" title="6. ps命令"></a>6. ps命令</h3><p>ps -a显示当前服务器所有用户进程</p>
<p>ps -a|grep java显示所有java进程</p>
<h3 id="7-top命令"><a href="#7-top命令" class="headerlink" title="7. top命令"></a>7. top命令</h3><p>用于动态地监视进程活动与系统负载等信息</p>
<h3 id="8-pidof命令"><a href="#8-pidof命令" class="headerlink" title="8. pidof命令"></a>8. pidof命令</h3><p>pidof docker查看docker进程的PID值（进程ID）</p>
<h3 id="9-kill命令"><a href="#9-kill命令" class="headerlink" title="9. kill命令"></a>9. kill命令</h3><p>kill PID值杀死PID值的进程</p>
<h3 id="10-killall命令"><a href="#10-killall命令" class="headerlink" title="10. killall命令"></a>10. killall命令</h3><p>killall docker杀死docker的所有进程</p>
<h3 id="11-ifconfig命令"><a href="#11-ifconfig命令" class="headerlink" title="11. ifconfig命令"></a>11. ifconfig命令</h3><p>查看本机当前的网卡配置与网络状态等信息时，其实主要查看的就是网卡名称、inet参数后面的IP地址、ether参数后面的网卡物理地址（又称为MAC地址），以及RX、TX的接收数据包与发送数据包的个数及累计流量</p>
<h3 id="12-uname命令"><a href="#12-uname命令" class="headerlink" title="12. uname命令"></a>12. uname命令</h3><p>uname -a查看系统内核信息与系统版本等信息</p>
<h3 id="13-uptime命令"><a href="#13-uptime命令" class="headerlink" title="13. uptime命令"></a>13. uptime命令</h3><p>查看系统负载</p>
<h3 id="14-free命令"><a href="#14-free命令" class="headerlink" title="14. free命令"></a>14. free命令</h3><p>free -h查看当前系统中内存的使用量信息</p>
<h3 id="15-who命令"><a href="#15-who命令" class="headerlink" title="15. who命令"></a>15. who命令</h3><p>查看所有正在登录本机的用户的名称以及他们正在开启的终端信息</p>
<h3 id="16-last命令"><a href="#16-last命令" class="headerlink" title="16. last命令"></a>16. last命令</h3><p>查看本机的登录记录</p>
<h3 id="17-history命令"><a href="#17-history命令" class="headerlink" title="17. history命令"></a>17. history命令</h3><p>当前用户在本地计算机中执行过的最近1000条命令记录</p>
<h3 id="18-sosreport命令"><a href="#18-sosreport命令" class="headerlink" title="18. sosreport命令"></a>18. sosreport命令</h3><p>收集系统配置及架构信息并输出诊断文档</p>
<h3 id="19-pwd命令"><a href="#19-pwd命令" class="headerlink" title="19. pwd命令"></a>19. pwd命令</h3><p>查看当前所处的工作目录</p>
<h3 id="20-cd命令"><a href="#20-cd命令" class="headerlink" title="20. cd命令"></a>20. cd命令</h3><p>cd &#x2F;usr切换到usr工作目录</p>
<p>cd ..&#x2F;返回上一级工作目录</p>
<p>cd ~切换到当前用户的家目录</p>
<h3 id="21-ls命令"><a href="#21-ls命令" class="headerlink" title="21. ls命令"></a>21. ls命令</h3><p>显示目录中的文件信息</p>
<p>ls -al：“-a”参数看到全部文件（包括隐藏文件），使用“-l”参数可以查看文件的属性、大小等详细信息。</p>
<h3 id="22-ll命令"><a href="#22-ll命令" class="headerlink" title="22. ll命令"></a>22. ll命令</h3><p>等同于ls -al</p>
<h3 id="23-cat命令"><a href="#23-cat命令" class="headerlink" title="23. cat命令"></a>23. cat命令</h3><p>用于查看纯文本文件（内容较少的），-n ：显示行号</p>
<h3 id="24-more命令"><a href="#24-more命令" class="headerlink" title="24. more命令"></a>24. more命令</h3><p>more命令会在最下面使用百分比的形式来提示您已经阅读了多少内容，还可以使用空格键或回车键向下翻页</p>
<h3 id="25-head命令"><a href="#25-head命令" class="headerlink" title="25. head命令"></a>25. head命令</h3><p>head -n 20 redis.conf 查看文件前20行</p>
<h3 id="26-tail命令"><a href="#26-tail命令" class="headerlink" title="26. tail命令"></a>26. tail命令</h3><p>tail -n 20 redis.conf查看文件后20行</p>
<p>tail -f hello.log查看实时最新日志文件时</p>
<h3 id="27-tr命令"><a href="#27-tr命令" class="headerlink" title="27. tr命令"></a>27. tr命令</h3><p>替换文本文件中的字符，格式为“tr [原始字符] [目标字符]”</p>
<p>cat anaconda-ks.cfg | tr [a-z] [A-Z]把文件小写字母替换成大写</p>
<h3 id="28-wc命令"><a href="#28-wc命令" class="headerlink" title="28. wc命令"></a>28. wc命令</h3><p>统计指定文本的行数（-l）、字数（-w）、字节数（-c），格式为“wc [参数] 文本”</p>
<h3 id="29-stat命令"><a href="#29-stat命令" class="headerlink" title="29. stat命令"></a>29. stat命令</h3><p>文件的具体存储信息和时间等信息，格式为“stat 文件名称”</p>
<h3 id="30-cut命令"><a href="#30-cut命令" class="headerlink" title="30. cut命令"></a>30. cut命令</h3><p>按“列”提取文本字符，格式为“cut [参数] 文本”</p>
<h3 id="31-diff命令"><a href="#31-diff命令" class="headerlink" title="31. diff命令"></a>31. diff命令</h3><p>先使用cat命令分别查看diff_A.txt和diff_B.txt文件的内容<br>cat diff_A.txt<br>cat diff_B.txt<br>接下来使用diff ——brief命令显示比较后的结果，判断文件是否相同<br>diff ——brief diff_A.txt diff_B.txt<br>最后使用带有-c参数的diff命令来描述文件内容具体的不同<br>diff -c diff_A.txt diff_B.txt</p>
<h3 id="32-touch命令"><a href="#32-touch命令" class="headerlink" title="32. touch命令"></a>32. touch命令</h3><p>创建空白文件或设置文件的时间，格式为“touch [选项] [文件]”</p>
<p>-a：修改读取时间<br>-m：修改修改时间<br>-d：同时修改atime与mtime</p>
<h3 id="33-mkdir命令"><a href="#33-mkdir命令" class="headerlink" title="33. mkdir命令"></a>33. mkdir命令</h3><p>创建空白的目录，格式为“mkdir [选项] 目录”<br>-p：创建递归目录</p>
<h3 id="34-cp命令"><a href="#34-cp命令" class="headerlink" title="34. cp命令"></a>34. cp命令</h3><p>复制文件或目录，格式为“cp [选项] 源文件 目标文件</p>
<h3 id="35-mv命令"><a href="#35-mv命令" class="headerlink" title="35. mv命令"></a>35. mv命令</h3><p>剪切文件或将文件重命名，格式为“mv [选项] 源文件 [目标路径|目标文件名]”</p>
<h3 id="36-rm命令"><a href="#36-rm命令" class="headerlink" title="36. rm命令"></a>36. rm命令</h3><p>用于删除文件或目录，格式为“rm [选项] 文件”</p>
<h3 id="37-dd命令"><a href="#37-dd命令" class="headerlink" title="37. dd命令"></a>37. dd命令</h3><p>用于按照指定大小和个数的数据块来复制文件或转换文件，格式为“dd [参数]”</p>
<h3 id="38-file命令"><a href="#38-file命令" class="headerlink" title="38. file命令"></a>38. file命令</h3><p>查看文件的类型，格式为“file 文件名”</p>
<h3 id="39-tar命令"><a href="#39-tar命令" class="headerlink" title="39. tar命令"></a>39. tar命令</h3><p>“tar -czvf 压缩包名称.tar.gz 要打包的目录”命令把指定的文件进行打包压缩；相应的解压命令为“tar -xzvf 压缩包名称.tar.gz”</p>
<h3 id="40-grep命令"><a href="#40-grep命令" class="headerlink" title="40. grep命令"></a>40. grep命令</h3><p>在文本中执行关键词搜索，并显示匹配的结果，格式为“grep [选项] [文件]”<br>-n参数用来显示搜索到信息的行号；<br>-v参数用于反选信息（即没有包含关键词的所有信息行）</p>
<h3 id="41-find命令"><a href="#41-find命令" class="headerlink" title="41. find命令"></a>41. find命令</h3><p>按照指定条件来查找文件，格式为“find [查找路径] 寻找条件 操作”</p>
<p>获取到该目录中所有以host开头的文件列表<br>find &#x2F;etc -name “host*” -print</p>
<p>在整个文件系统中找出所有归属于linuxprobe用户的文件并复制到&#x2F;root&#x2F;findresults目录<br>find &#x2F; -user linuxprobe -exec cp -a {} &#x2F;root&#x2F;findresults&#x2F; ;</p>
<h3 id="42-chmod命令"><a href="#42-chmod命令" class="headerlink" title="42.chmod命令"></a>42.chmod命令</h3><p>能够用来设置文件或目录的权限，格式为“chmod [参数] 权限 文件或目录名称”。</p>
<h3 id="43-chown命令"><a href="#43-chown命令" class="headerlink" title="43.chown命令"></a>43.chown命令</h3><p>设置文件或目录的所有者和所属组，其格式为“chown [参数] 所有者：所属组 文件或目录名称”。chmod、chown大写参数-R来表示递归操作，即对目录内所有的文件进行整体操作。</p>
<h2 id="管道符、重定向和环境变量"><a href="#管道符、重定向和环境变量" class="headerlink" title="管道符、重定向和环境变量"></a>管道符、重定向和环境变量</h2><h3 id="输入输出重定向"><a href="#输入输出重定向" class="headerlink" title="输入输出重定向"></a>输入输出重定向</h3><ul>
<li>标准输入重定向（STDIN，文件描述符为0）：默认从键盘输入，也可从其他文件或命令中输入。</li>
<li>标准输出重定向（STDOUT，文件描述符为1）：默认输出到屏幕。</li>
<li>错误输出重定向（STDERR，文件描述符为2）：默认输出到屏幕。</li>
</ul>
<p>输出：</p>
<ol>
<li>命令 &lt; 文件：将文件作为命令的标准输入</li>
<li>命令 &lt;&lt; 分界符：从标准输入中读入，直到遇见分界符才停止</li>
<li>命令 &lt; 文件1 &gt; 文件2：将文件1作为命令的标准输入并将标准输出到文件2</li>
</ol>
<p>输出：</p>
<ol>
<li>命令 &gt; 文件：将文件标准输出重定向到一个文件中（清空原文件的数据）</li>
<li>命令2 &gt; 文件：将错误输出重定向到一个文件中（清空原文件的数据）</li>
<li>命令 &gt;&gt; 文件：将标准输出重定向到一个文件中（追加到原有内容的后面）</li>
<li>命令2 &gt;&gt; 文件：将错误输出重定向到一个文件中（追加到原有内容的后面）</li>
<li>命令 &gt;&gt; 文件2 &gt; &amp;1（命令&amp; &gt;&gt; 文件）：将标准输出与错误输出共同写入到文件中（追加到原有内容后面）</li>
</ol>
<h3 id="管道命令符（-）"><a href="#管道命令符（-）" class="headerlink" title="管道命令符（|）"></a>管道命令符（|）</h3><p>执行格式为“命令A | 命令B”。命令符的作用也可以用一句话来概括“把前一个命令原本要输出到屏幕的数据当作是后一个命令的标准输入”。</p>
<p>用翻页的形式查看&#x2F;etc目录中的文件列表及属性信息（这些内容默认会一股脑儿地显示到屏幕上，根本看不清楚）<br>ls -l &#x2F;etc&#x2F; | more</p>
<h3 id="转义字符"><a href="#转义字符" class="headerlink" title="转义字符"></a>转义字符</h3><ul>
<li><p>反斜杠（\）：使反斜杠后面的一个变量变为单纯的字符串。</p>
<p>echo “Price is \$$PRICE”</p>
<p>Price is  $5</p>
</li>
<li><p>单引号（’’）：转义其中所有的变量为单纯的字符串。</p>
</li>
<li><p>双引号（””）：保留其中的变量属性，不进行转义处理。</p>
<p>PRICE&#x3D;5</p>
<p>echo “Price is $PRICE”</p>
<p>Price is 5</p>
</li>
<li><p>反引号（&#96;&#96;）：把其中的命令执行后返回结果。</p>
<p>echo `uname -a`</p>
</li>
</ul>
<h3 id="环境变量"><a href="#环境变量" class="headerlink" title="环境变量"></a>环境变量</h3><p>命令在Linux中的执行分为4个步骤。</p>
<p>第1步：判断用户是否以绝对路径或相对路径的方式输入命令（如&#x2F;bin&#x2F;ls），如果是的话则直接执行。</p>
<p>第2步：Linux系统检查用户输入的命令是否为“别名命令”，即用一个自定义的命令名称来替换原本的命令名称。可以用alias命令来创建一个属于自己的命令别名，格式为“alias 别名&#x3D;命令”。若要取消一个命令别名，则是用unalias命令，格式为“unalias 别名”。我们之前在使用rm命令删除文件时，Linux系统都会要求我们再确认是否执行删除操作，其实这就是Linux系统为了防止用户误删除文件而特意设置的rm别名命令，接下来我们把它取消掉：<br>[root@linuxprobe ～]# ls<br>anaconda-ks.cfg Documents initial-setup-ks.cfg Pictures Templates<br>Desktop Downloads Music Public Videos<br>[root@linuxprobe ～]# rm anaconda-ks.cfg<br>rm：remove regular file ‘anaconda-ks.cfg’? y<br>[root@linuxprobe～]# alias rm<br>alias rm&#x3D;’rm -i’<br>[root@linuxprobe ～]# unalias rm<br>[root@linuxprobe ～]# rm initial-setup-ks.cfg<br>[root@linuxprobe ～]#</p>
<p>第3步：Bash解释器判断用户输入的是内部命令还是外部命令。内部命令是解释器内部的指令，会被直接执行；而用户在绝大部分时间输入的是外部命令，这些命令交由步骤4继续处理。可以使用“type命令名称”来判断用户输入的命令是内部命令还是外部命令。</p>
<p>第4步：系统在多个路径中查找用户输入的命令文件，而定义这些路径的变量叫作PATH，可以简单地把它理解成是“解释器的小助手”，作用是告诉Bash解释器待执行的命令可能存放的位置，然后Bash解释器就会乖乖地在这些位置中逐个查找。</p>
<ul>
<li>HOME：用户的主目录</li>
<li>SHELL：用户在使用的Shell解释器名称</li>
<li>HISTSIZE：输出的历史命令记录条数</li>
<li>HISTFILESIZE：保存的历史命令记录条数</li>
<li>MAIL：邮件保存路径</li>
<li>LANG：系统语言、语系名称</li>
<li>RANDOM：生成一个随机数字</li>
<li>PS1：Bash解释器的提示符</li>
<li>PATH：定义解释器搜索用户执行命令的路径</li>
<li>EDITOR：用户默认的文本编辑器</li>
</ul>
<h2 id="Vim编辑器和Shell命令脚本"><a href="#Vim编辑器和Shell命令脚本" class="headerlink" title="Vim编辑器和Shell命令脚本"></a>Vim编辑器和Shell命令脚本</h2><ul>
<li><p>命令模式：控制光标移动，可对文本进行复制、粘贴、删除和查找等工作。（a、i、o等键进入输入模式；：键进入末行模式）</p>
<p>dd：删除光标所在行<br>5dd：删除光标处开始的5行<br>yy：复制光标所在整行<br>5yy：复制从光标处开始的5行<br>n：显示搜索命令定位到下一个字符串<br>N：显示搜索命令定位到上一个字符串<br>u：撤销上一步操作<br>p：将之前删除或复制过的数据粘贴到光标后面</p>
</li>
<li><p>输入模式：正常的文本录入。（Esc键退出输入模式返回命令模式）</p>
<p>:w：保存</p>
<p>:q：退出</p>
<p>:q!：强制退出（放弃修改内容）<br>:wq!：强制保存退出<br>:set nu：显示行号<br>:set nonu：不显示行号</p>
<p>:命令：执行该命令</p>
<p>:整数：跳转到该行</p>
<p>:s&#x2F;one&#x2F;two：将当前光标所在行的第一个one替换成two<br>:%s&#x2F;one&#x2F;two&#x2F;g：将文中所有one替换成two<br>?字符串：在文中向上搜索该字符串<br>&#x2F;字符串：在文中向下搜索该字符串</p>
</li>
<li><p>末行模式：保存或退出文档，以及设置编辑环境。（Esc键退出输入模式返回命令模式）</p>
</li>
</ul>
<h3 id="Shell脚本"><a href="#Shell脚本" class="headerlink" title="Shell脚本"></a>Shell脚本</h3><p>[root@～]# vim example.sh<br>#！&#x2F;bin&#x2F;bash<br>#For Example BY linuxprobe.com<br>pwd<br>ls -al</p>
<p>第一行的脚本声明（#！）用来告诉系统使用哪种Shell解释器来执行该脚本；第二行的注释信息（#）是对脚本功能和某些命令的介绍信息，使得自己或他人在日后看到这个脚本内容时，可以快速知道该脚本的作用或一些警告信息；第三、四行的可执行语句也就是我们平时执行的Linux命令了。</p>
<p>[root@～]# vim example.sh<br>#！&#x2F;bin&#x2F;bash<br>echo “当前脚本名称为$0”<br>echo “总共有$#个参数，分别是$*。”<br>echo “第1个参数为$1，第5个为$5。”<br>[root@～]# sh example.sh one two three four five six</p>
<p>当前脚本名称为example.sh<br>总共有6个参数，分别是one two three four five six。<br>第1个参数为one，第5个为five。</p>
<p>Shell脚本中的条件测试语法可以判断表达式是否成立，若条件成立则返回数字0，否则便返回其他随机数值。测试语句格式：</p>
<p>[ 条件表达式 ]</p>
<ul>
<li><p>文件测试语句；</p>
<p>-d：测试文件是否为目录类型</p>
<p>-e：测试文件是否存在</p>
<p>-f：判断是否为一般文件</p>
<p>-r：测试当前用户是否有权限读取</p>
<p>-w：测试当前用户是否有权限写入</p>
<p>-x：测试当前用户是否有权限执行</p>
</li>
<li><p>逻辑测试语句；</p>
<p>&amp;&amp;：“与”运算符</p>
<p>||：“或”运算符</p>
<p>!：“非”运算符</p>
</li>
<li><p>整数值比较语句；</p>
<p>-eq：是否等于</p>
<p>-ne：是否不等于</p>
<p>-gt：是否大于</p>
<p>-lt：是否小于</p>
<p>-le：是否等于或小于</p>
<p>-ge：是否大于或等于</p>
</li>
<li><p>字符串比较语句。</p>
<p>&#x3D;：比较字符串内容是否相同</p>
<p>!&#x3D;：比较字符串内容是否不同</p>
<p>-z：判断字符串内容是否为空</p>
</li>
</ul>
<h3 id="流程控制语句"><a href="#流程控制语句" class="headerlink" title="流程控制语句"></a>流程控制语句</h3><ul>
<li><p>if</p>
<p>#！&#x2F;bin&#x2F;bash<br>read -p “Enter your score（0-100）：” GRADE<br>if [ $GRADE -ge 85 ] &amp;&amp; [ $GRADE -le 100 ] ; then<br>  echo “$GRADE is Excellent”<br>elif [ $GRADE -ge 70 ] &amp;&amp; [ $GRADE -le 84 ] ; then<br>  echo “$GRADE is Pass”<br>else<br>  echo “$GRADE is Fail”<br>fi</p>
</li>
<li><p>for</p>
<p>for 变量名 in 取值列表</p>
<p>do</p>
<p>​    命令序列</p>
<p>done</p>
</li>
<li><p>while</p>
<p>whlie 条件测试操作</p>
<p>do</p>
<p>​    命令序列</p>
<p>done</p>
</li>
<li><p>case</p>
<p>case 变量值 in</p>
<p>模式1)</p>
<p>​    命令序列1</p>
<p>​    ;;</p>
<p>模式2)</p>
<p>​    命令序列2</p>
<p>​    ;;</p>
<p>….</p>
<p>*)</p>
<p>​    默认命令序列</p>
<p>esac</p>
</li>
</ul>
<h3 id="计划任务服务程序"><a href="#计划任务服务程序" class="headerlink" title="计划任务服务程序"></a>计划任务服务程序</h3><ul>
<li><p>一次性计划任务：</p>
<p>[root@linuxprobe ～]# at 23：30<br>at &gt; systemctl restart httpd<br>at &gt; 此处请同时按下Ctrl + D组合键来结束编写计划任务</p>
<p>at -l查看还没执行命令</p>
<p>atrm任务id，删除任务</p>
</li>
<li><p>周期性计划任务</p>
<p>crontab -e：创建、编辑任务</p>
<p>crontab -l：查看任务</p>
<p>crontab -r：删除任务</p>
</li>
</ul>
<h2 id="用户与文件权限"><a href="#用户与文件权限" class="headerlink" title="用户与文件权限"></a>用户与文件权限</h2><h3 id="用户权限命令"><a href="#用户权限命令" class="headerlink" title="用户权限命令"></a>用户权限命令</h3><ul>
<li><p>useradd命令</p>
<p>创建新的用户，格式为“useradd [选项] 用户名”<br>-d：指定用户的家目录<br>-e：账户的到期时间，格式我为YYYY-MM-DD<br>-u：指定该用户的默认<br>-g：指定一个初始的用户的基本组<br>-G：指定一个或多个扩展用户组<br>-N：不创建与用户同名的基本用户组<br>-s：指定该用户的默认Shell解释器</p>
</li>
<li><p>groupadd命令</p>
<p>用于创建用户组，格式为“groupadd [选项] 群组名”</p>
</li>
<li><p>usermod命令</p>
<p>用于修改用户的属性，格式为“usermod [选项] 用户名”<br>-c：填写用户账户的备注信<br>-d、-m：参数-m与参数-d连用，可重新指定用户的家目录并自动把旧的数据转移过去<br>-e：账户的到期时间格式为YYYY-MM-DD<br>-g：变更所属用户组<br>-G：变更扩展用户组<br>-L：锁定用户禁止其登录系统<br>-U：解锁2用户，允许其登录系统<br>-s：变更默认终端<br>-u：修改用户的UID</p>
</li>
<li><p>passwd命令</p>
<p>用于修改用户密码、过期时间、认证信息等，格式为“passwd [选项] [用户名]”<br>-l：锁定用户，禁止其登录<br>-u：解除锁定，允许用户登录<br>–stdin：允许通过标准输入修改用户密码，如echo “NewPassWord” passwd –stdin UserName<br>-d：使用该用户可用空密码登录系统<br>-e：强制用户在下一次登录时修改密码<br>-S：显示用户的密码是否被锁定，以及密码所采用的加密算法名称</p>
</li>
<li><p>userdel命令</p>
<p>用于删除用户，格式为“userdel [选项] 用户名”<br>-f：强制删除用户<br>-r：强制删除用户及用户家目录</p>
</li>
</ul>
<h3 id="文件权限"><a href="#文件权限" class="headerlink" title="文件权限"></a>文件权限</h3><ul>
<li>-：普通文件</li>
<li>d：目录文件</li>
<li>l：链接文件</li>
<li>b：块设备文件</li>
<li>c：字符设备文件</li>
<li>p：管道文件</li>
</ul>
<p>r、w、x分别表示可读、可写、可执行，也可用4、2、1表示<br>现在有这样一个文件，其所有者拥有可读、可写、可执行的权限，其文件所属组拥有可读、可写的权限；而且其他人只有可读的权限。那么，这个文件的权限就是rwxrw-r——，数字法表示即为764。<br>-rw-r–r– 1 root    root 58763 Dec  3 15:30 redis.conf<br>文件的类型、访问权限、所有者（属主）、所属组（属组）、占用的磁盘大小、修改时间和文件名称等信息，通过分析可知，该文件的类型为普通文件，所有者权限为可读、可写（rw-），所属组权限为可读（r——），除此以外的其他人也只有可读权限（r——），文件的磁盘占用大小是58763字节，最近一次的修改时间为12月3日的15点30分，文件的名称为redis.conf。</p>
<h3 id="文件特殊权限"><a href="#文件特殊权限" class="headerlink" title="文件特殊权限"></a>文件特殊权限</h3><p>SUID是一种对二进制程序进行设置的特殊权限，可以让二进制程序的执行者临时拥有属主的权限（仅对拥有执行权限的二进制程序有效）。<br>查看passwd命令属性时发现所有者的权限由rwx变成了rws，其中x改变成s就意味着该文件被赋予了SUID权限。如果原先权限位上没有x执行权限，那么被赋予特殊权限后将变成大写的S。</p>
<p>SGID</p>
<ul>
<li>让执行者临时拥有属组的权限（对拥有执行权限的二进制程序进行设置）；</li>
<li>在某个目录中创建的文件自动继承该目录的用户组（只可以对目录进行设置）。</li>
<li>对应的参数o+t代表设置SGID，如：chmod -Rf g+s testdir&#x2F;</li>
</ul>
<p>SBIT</p>
<p>SBIT特殊权限位可确保用户只能删除自己的文件，而不能删除其他用户的文件。用chmod命令就可以了。对应的参数o+t代表设置SBIT粘滞位权，如：chmod -R o+t linux&#x2F;</p>
<h3 id="文件隐藏属性"><a href="#文件隐藏属性" class="headerlink" title="文件隐藏属性"></a>文件隐藏属性</h3><ul>
<li>chattr命令<br>用于设置文件的隐藏权限，格式为“chattr [参数] 文件”，如果想要把某个隐藏功能添加到文件上，则需要在命令后面追加“+参数”，如果想要把某个隐藏功能移出文件，则需要追加“-参数”。<br>i：无法对文件进行修改；若对目录设置了该参数，则仅能修改其中的子文件内容而不能新建或删除文件<br>a：仅允许补充（追加）内容，无法覆盖&#x2F;删除内容<br>S：文件内容在变更后立即同步到硬盘<br>s：彻底从硬盘中删除，不可恢复<br>A：不再修改这个文件或目录的最后访问时间<br>b：不再修改这个文件或目录的存取时间<br>D：检查压缩文件中的错误<br>d：使用dump命令备份是忽略文件&#x2F;目录<br>c：默认将文件或目录进行压缩<br>u：当删除该文件后依然保留其在硬盘中的数据，方便日后恢复<br>t：让文件系统支持尾部合并<br>X：可以直接访问压缩文件中的内容</li>
<li>lsattr命令<br>显示文件的隐藏权限，格式为“lsattr [参数] 文件”</li>
</ul>
<h3 id="文件访问控制列表"><a href="#文件访问控制列表" class="headerlink" title="文件访问控制列表"></a>文件访问控制列表</h3><ul>
<li><p>setfacl命令</p>
<p>用于管理文件的ACL规则，格式为“setfacl [参数] 文件名称”，其中，针对目录文件需要使用-R递归参数；针对普通文件则使用-m参数；如果想要删除某个文件的ACL，则可以使用-b参数。</p>
</li>
<li><p>getfacl命令</p>
<p>用于显示文件上设置的ACL信息，格式为“getfacl 文件名称”</p>
</li>
</ul>
<h3 id="su命令与sudo服务"><a href="#su命令与sudo服务" class="headerlink" title="su命令与sudo服务"></a>su命令与sudo服务</h3><ul>
<li>su<br>su userId：切换到userId用户，如果su命令与用户名之间有一个减号（-），这意味着完全切换到新的用户，即把环境变量信息也变更为新用户的相应信息，而不是保留原始的信息。强烈建议在切换用户身份时添加这个减号（-）。</li>
<li>sudo<br>给普通用户提供额外的权限来完成原本root管理员才能完成的任务，格式为“sudo [参数] 命令名称”<br>-h：列出帮助信息<br>-l：列出当前用户可执行的命令<br>-u 用户名或UID值：以指定用户身份执行命令<br>-k：清空密码的有效时间，下一次执行sudo时需要再次进行密码验证<br>-b：在后台执行指定的命令<br>-p：更改询问密码的提示语</li>
</ul>
<h2 id="存储结构与磁盘划分"><a href="#存储结构与磁盘划分" class="headerlink" title="存储结构与磁盘划分"></a>存储结构与磁盘划分</h2><h3 id="一切从“-x2F-”开始"><a href="#一切从“-x2F-”开始" class="headerlink" title="一切从“&#x2F;”开始"></a>一切从“&#x2F;”开始</h3><p>Linux系统中的一切文件都是从“根（&#x2F;）”目录开始的，并按照文件系统层次化标准（FHS）采用树形结构来存放文件，以及定义了常见目录的用途。另外，Linux系统中的文件和目录名称是严格区分大小写的。例如，root、rOOt、Root、rooT均代表不同的目录，并且文件名称中不得包含斜杠（&#x2F;）<br>&#x2F;boot：开机所需文件——内核、开机菜单以及所需配置文件等<br>&#x2F;dev：以文件形式存放任何设备与接口<br>&#x2F;etc：配置文件<br>&#x2F;home：用户家目录<br>&#x2F;bin：存放单用户模式下还可以操作的命令<br>&#x2F;lib：开机时用到的函数库，以及&#x2F;bin与&#x2F;sbin下面的命令要调用的函数<br>&#x2F;sbin：开机过程中需要的命令<br>&#x2F;media：用于挂载设备文件的目录<br>&#x2F;opt：放置第三方的软件<br>&#x2F;root：系统管理员的家目录<br>&#x2F;srv：一些网络服务的数据文件目录<br>&#x2F;tmp：任何人均可使用的“共享”临时目录<br>&#x2F;proc：虚拟文件系统，例如系统内核、进程、外部设备及网络状态等<br>&#x2F;usr&#x2F;local：用户自行安装的软件<br>&#x2F;usr&#x2F;sbinLinux系统开机时不会使用到的软件&#x2F;命令&#x2F;脚本<br>&#x2F;usr&#x2F;share：帮助与说明文件。也可放置共享文件<br>&#x2F;var：主要存放经常变化的文件，如日志<br>&#x2F;lost+found：当文件系统发生错误时，将一些丢失的文件片段存放在这里</p>
<h3 id="物理设备的命令规则"><a href="#物理设备的命令规则" class="headerlink" title="物理设备的命令规则"></a>物理设备的命令规则</h3><p>系统内核中的udev设备管理器会自动把硬件名称规范起来，目的是让用户通过设备文件的名字可以猜出设备大致的属性以及分区信息等</p>
<p>udev设备管理器的服务会一直以守护进程的形式运行并侦听内核发出的信号来管理&#x2F;dev目录下的设备文件。<br>IDE设备：&#x2F;dev&#x2F;hd[a-d]<br>SCSI&#x2F;SATA&#x2F;U盘：&#x2F;dev&#x2F;sd[a-p]<br>软驱：&#x2F;dev&#x2F;fd[0-1]<br>打印机：&#x2F;dev&#x2F;lp[0-15]<br>光驱：&#x2F;dev&#x2F;cdrom<br>鼠标：&#x2F;dev&#x2F;mouse<br>磁带机：&#x2F;dev&#x2F;st0或&#x2F;dev&#x2F;ht0</p>
<p>a~p代表16块不同的硬盘</p>
<ul>
<li>主分区或扩展分区的编号从1开始，到4结束；</li>
<li>逻辑分区从编号5开始。</li>
</ul>
<p>“&#x2F;dev&#x2F;sda5”表示的就是“这是系统中第一块被识别到的硬件设备中分区编号为5的逻辑分区的设备文件”。</p>
<h3 id="文件系统与数据资料"><a href="#文件系统与数据资料" class="headerlink" title="文件系统与数据资料"></a>文件系统与数据资料</h3><p>用户在硬件存储设备中执行的文件建立、写入、读取、修改、转存与控制等操作都是依靠文件系统来完成的。文件系统的作用是合理规划硬盘，以保证用户正常的使用需求。</p>
<ul>
<li>Ext3：是一款日志文件系统，能够在系统异常宕机时避免文件系统资料丢失，并能自动修复数据的不一致与错误。然而，当硬盘容量较大时，所需的修复时间也会很长，而且也不能百分之百地保证资料不会丢失。它会把整个磁盘的每个写入动作的细节都预先记录下来，以便在发生异常宕机后能回溯追踪到被中断的部分，然后尝试进行修复。</li>
<li>Ext4：Ext3的改进版本，作为RHEL 6系统中的默认文件管理系统，它支持的存储容量高达1EB（1EB&#x3D;1，073，741，824GB），且能够有无限多的子目录。另外，Ext4文件系统能够批量分配block块，从而极大地提高了读写效率。</li>
<li>XFS：是一种高性能的日志文件系统，而且是RHEL 7中默认的文件管理系统，它的优势在发生意外宕机后尤其明显，即可以快速地恢复可能被破坏的文件，而且强大的日志功能只用花费极低的计算和存储性能。并且它最大可支持的存储容量为18EB，这几乎满足了所有需求。</li>
</ul>
<h3 id="挂载硬件设备"><a href="#挂载硬件设备" class="headerlink" title="挂载硬件设备"></a>挂载硬件设备</h3><ul>
<li>mount命令<br>用于挂载文件系统，格式为“mount 文件系统 挂载目录”，mount 中的-a参数则厉害了，它会在执行后自动检查&#x2F;etc&#x2F;fstab文件中有无疏漏被挂载的设备文件，如果有，则进行自动挂载操作。</li>
<li>unmount命令<br>撤销已经挂载的设备文件，格式为“umount [挂载点&#x2F;设备文件]”。</li>
</ul>
<h3 id="添加硬盘设备"><a href="#添加硬盘设备" class="headerlink" title="添加硬盘设备"></a>添加硬盘设备</h3><ul>
<li>fdisk命令<br>用于管理磁盘分区，格式为“fdisk [磁盘名称]”<br>m：查看全部可用的参数<br>n：添加新的分区<br>d：删除某个分区信息<br>l：列出所有可用的分区类型<br>t：改变某个分区的类型<br>p：查看分区信息<br>w：保存并退出<br>q：不保存直接退出</li>
<li>du命令<br>用于查看文件数据占用量的du命令，其格式为“du [选项] [文件]”，du -sh &#x2F;*命令来查看在Linux系统根目录下所有一级目录分别占用的空间大小。</li>
</ul>
<h3 id="添加交换分区"><a href="#添加交换分区" class="headerlink" title="添加交换分区"></a>添加交换分区</h3><p>SWAP（交换）分区是一种通过在硬盘中预先划分一定的空间，然后将把内存中暂时不常用的数据临时存放到硬盘中，以便腾出物理内存空间让更活跃的程序服务来使用的技术，其设计目的是为了解决真实物理内存不足的问题。在生产环境中，交换分区的大小一般为真实物理内存的1.5～2倍。</p>
<h3 id="软硬方式链接"><a href="#软硬方式链接" class="headerlink" title="软硬方式链接"></a>软硬方式链接</h3><ul>
<li><p>硬链接（hard link）：可以将它理解为一个“指向原始文件inode的指针”，系统不为它分配独立的inode和文件。所以，硬链接文件与原始文件其实是同一个文件，只是名字不同。我们每添加一个硬链接，该文件的inode连接数就会增加1；而且只有当该文件的inode连接数为0时，才算彻底将它删除。换言之，由于硬链接实际上是指向原文件inode的指针，因此即便原始文件被删除，依然可以通过硬链接文件来访问。需要注意的是，由于技术的局限性，我们不能跨分区对目录文件进行链接。</p>
</li>
<li><p>软链接（也称为符号链接[symbolic link]）：仅仅包含所链接文件的路径名，因此能链接目录文件，也可以跨越文件系统进行链接。但是，当原始文件被删除后，链接文件也将失效，从这一点上来说与Windows系统中的“快捷方式”具有一样的性质。</p>
</li>
</ul>
<p>ln命令用于创建链接文件，格式为“ln [选项] 目标”<br>-s：创建“符号链接”（如果不带-s参数，则默认创建硬链接）<br>-f：强制创建文件或目录的链接<br>-i：覆盖前先询问<br>-v：显示创建链接的过程</p>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  


  



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="xxstar"
      src="/xx1997/images/xxstar.jpg">
  <p class="site-author-name" itemprop="name">xxstar</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/xx1997/archives/">
        
          <span class="site-state-item-count">6</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/xx1997/categories/">
          
        <span class="site-state-item-count">1</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/xx1997/tags/">
          
        <span class="site-state-item-count">5</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="/xx1997/wx1051337247@163.com" title="E-Mail → wx1051337247@163.com"><i class="fa fa-envelope fa-fw"></i>E-Mail</a>
      </span>
  </div>
  <div class="cc-license motion-element" itemprop="license">
    <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" class="cc-opacity" rel="noopener" target="_blank"><img src="/xx1997/images/cc-by-nc-sa.svg" alt="Creative Commons"></a>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">xxstar</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Gemini</a> 强力驱动
  </div>

        
<div class="busuanzi-count">
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
    <span class="post-meta-item" id="busuanzi_container_site_uv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-user"></i>
      </span>
      <span class="site-uv" title="总访客量">
        <span id="busuanzi_value_site_uv"></span>
      </span>
    </span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item" id="busuanzi_container_site_pv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-eye"></i>
      </span>
      <span class="site-pv" title="总访问量">
        <span id="busuanzi_value_site_pv"></span>
      </span>
    </span>
</div>








      </div>
    </footer>
  </div>

  
  <script src="/xx1997/lib/anime.min.js"></script>
  <script src="/xx1997/lib/velocity/velocity.min.js"></script>
  <script src="/xx1997/lib/velocity/velocity.ui.min.js"></script>

<script src="/xx1997/js/utils.js"></script>

<script src="/xx1997/js/motion.js"></script>


<script src="/xx1997/js/schemes/pisces.js"></script>


<script src="/xx1997/js/next-boot.js"></script>




  




  
<script src="/xx1997/js/local-search.js"></script>













  

  

</body>
</html>
